package builder

import (
	"binseek/pkg/finder"
	"binseek/pkg/utils"
	"bytes"
	"fmt"
	"io/ioutil"
	"sort"
	"strings"
	"time"
)

const BUILDER_HEAD_SIZE = 20 // 文件头20个字节

type Record struct {
	IndexData []byte // 6个字节表示IP段
	*Node
}

// Node 树状组织的节点
type Node struct {
	Name     string
	Offset   []byte
	Children map[string]*Node
}

func NewNode(name string) *Node {
	return &Node{
		Name: name, Offset: []byte{0xff, 0x00, 0x00},
		Children: make(map[string]*Node),
	}
}

func (d Node) Sorted() []string {
	var names []string
	for name := range d.Children {
		names = append(names, name)
	}
	sort.Strings(names)
	return names
}

func (d Node) BuildDists(offset int) []byte {
	data := append([]byte(d.Name), 0x00)
	backOffset := len(data) + 3 // city前缀偏移量字节数
	for _, name := range d.Sorted() {
		dist := d.Children[name]
		distOffset := offset + backOffset
		idxData := utils.PutUint32(distOffset, true)
		dist.Offset = idxData[1:]
		idxData = utils.PutUint32(backOffset, true)
		data = append(data, idxData[2], idxData[3])
		data = append(data, []byte(name)...)
		data = append(data, 0x00)
		backOffset += len(name) + 3 // dist前缀偏移量字节数和结束符
	}
	return data
}

func (d Node) BuildCitys(offset int) []byte {
	var data []byte
	for _, name := range d.Sorted() {
		city := d.Children[name]
		cityOffset := offset + len(data)
		idxData := utils.PutUint32(cityOffset, true)
		city.Offset = idxData[1:]
		data = append(data, d.Offset...)
		data = append(data, city.BuildDists(cityOffset)...)
	}
	return data
}

func ConcatState(isoCode, state string) string {
	if isoCode == "CN" || state == "" {
		return state
	}
	return fmt.Sprintf("(%s)%s", isoCode, state)
}

type Builder struct {
	headSize, indexSize, appendixSize int
	lastPos, firstPos, appendixPos    int
	Appendix                          finder.Glossary
	Records                           []*Record
	Root                              *Node
}

func NewBuilder(headSize int) *Builder {
	return &Builder{headSize: headSize, Root: NewNode("")}
}

func (b *Builder) AddRecord(record *Record) int {
	b.Records = append(b.Records, record)
	return len(b.Records)
}

func (b *Builder) BuildRecord(record *Record, state, city, district string) int {
	var node, cityNode, distNode *Node
	ok := false
	if node, ok = b.Root.Children[state]; !ok {
		node = NewNode(state)
		b.Root.Children[state] = node
	}
	if city == "" && district == "" {
		record.Node = node
		return b.AddRecord(record)
	}
	if cityNode, ok = node.Children[city]; !ok {
		cityNode = NewNode(city)
		node.Children[city] = cityNode
	}
	if district == "" {
		record.Node = cityNode
		return b.AddRecord(record)
	}
	if distNode, ok = cityNode.Children[district]; !ok {
		distNode = NewNode(district)
		cityNode.Children[district] = distNode
	}
	record.Node = distNode
	return b.AddRecord(record)
}

func (b *Builder) BuildHead() []byte {
	head := utils.PutUint32(b.lastPos, true)
	head = append(head, utils.PutUint32(b.firstPos, true)...)
	head = append(head, utils.PutUint32(b.appendixPos, true)...)
	head = append(head, []byte(time.Now().Format("20060102"))...)
	if len(head) > b.headSize {
		head = head[:b.headSize]
	} else if len(head) < b.headSize {
		zero := bytes.Repeat([]byte{0x00}, b.headSize-len(head))
		head = append(head, zero...)
	}
	return head
}

func (b *Builder) BuildBody() []byte {
	var body []byte
	for i, elem := range b.Appendix.Dict {
		name := string(bytes.TrimSuffix(elem, []byte{0x00}))
		if node, ok := b.Root.Children[name]; ok {
			idxData := utils.PutUint32(i, true)
			node.Offset = []byte{0xff, idxData[2], idxData[3]}
			utils.DebugPrint(name, i, node.Offset)
			offset := len(body) + b.headSize
			body = append(body, node.BuildCitys(offset)...)
		}
	}
	b.appendixPos = b.headSize + len(body)
	b.firstPos = b.appendixPos + b.appendixSize
	return body
}

func (b *Builder) BuildAppendix() []byte {
	var data []byte
	for name := range b.Root.Children {
		data = append(data, []byte(name)...)
		data = append(data, 0x00)
	}
	b.Appendix = finder.CreateGlossary(data)
	data = b.Appendix.Bytes()
	b.appendixSize = len(data)
	return data
}

func (b *Builder) BuildIndex() []byte {
	var record *Record
	if len(b.Records) > 0 {
		record = b.Records[0]
		b.indexSize = len(record.IndexData) + len(record.Offset)
	}
	var data []byte
	for _, record = range b.Records {
		data = append(data, record.IndexData...)
		data = append(data, record.Offset...)
	}
	b.lastPos = b.firstPos + len(data) - b.indexSize
	return data
}

func (b *Builder) BuildAll() []byte {
	appendix := b.BuildAppendix()
	body := b.BuildBody()
	data := append(body, appendix...)
	index := b.BuildIndex()
	data = append(data, index...)
	head := b.BuildHead()
	data = append(head, data...)

	utils.DebugPrint(utils.Bin2Hex(head), head)
	utils.DebugPrint(utils.Bin2Hex(body), body)
	utils.DebugPrint(utils.Bin2Hex(appendix), appendix)
	utils.DebugPrint(utils.Bin2Hex(index), index)
	return data
}

func BuildDataFile(sourceFile, targetFile string) (string, error) {
	fp, _, fail := utils.OpenFile(sourceFile)
	if fail != nil {
		return "", fail
	}
	defer fp.Close()

	n, lines, err := utils.ReadCsv(fp, "\t", -1)
	utils.DebugPrint(n, len(lines))

	builder := NewBuilder(BUILDER_HEAD_SIZE)
	var isoCode, state, city, district string
	for _, line := range lines {
		if utils.IgnoreLine(line, 4) {
			continue
		}
		record := new(Record)
		start, stop := utils.Ip2Bytes(line[0]), utils.Ip2Bytes(line[1])
		if bytes.Compare(start[:2], stop[:2]) == 0 {
			record.IndexData = append(start, stop[2], stop[3])
		} else { // IP段跨度大，改为用两个0忽略比较
			record.IndexData = append(start, 0x00, 0x00)
		}
		isoCode, state = strings.TrimSpace(line[2]), strings.TrimSpace(line[3])
		state = ConcatState(isoCode, state)
		if len(line) > 4 {
			city = strings.TrimSpace(line[4])
			if len(line) > 5 {
				district = strings.TrimSpace(line[5])
			}
		}
		builder.BuildRecord(record, state, city, district)
	}

	data := builder.BuildAll()
	err = ioutil.WriteFile(targetFile, data, 0755)
	return targetFile, err
}
